package com.sca.order.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.sca.order.constant.RedisKeyConstant;
import com.sca.order.entity.Seckill;
import com.sca.order.repository.SeckillMapper;
import com.sca.order.service.OrderService;
import com.sca.order.service.SeckillService;
import com.sca.order.service.StockService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * @author liuxianfei
 * @since 2020/11/3 11:51
 */
@Slf4j
@Service
public class SeckillServiceImpl extends ServiceImpl<SeckillMapper, Seckill> implements SeckillService {

    private final StockService stockService;
    private final OrderService orderService;
    private final RedisTemplate<String, Object> redisTemplate;

    public SeckillServiceImpl(StockService stockService, OrderService orderService, RedisTemplate<String, Object> redisTemplate) {
        this.stockService = stockService;
        this.orderService = orderService;
        this.redisTemplate = redisTemplate;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @SentinelResource(value = "seckill")
    public Boolean seckill(Integer userId, Long goodId) {
        /*
            必须得加@SentinelResource，不然无法监控该簇点，不知道为啥
         */
        Boolean subtract = stockService.subtract(goodId, 1);
        if (subtract) {
            Seckill seckill = new Seckill();
            seckill.setUserId(userId);
            seckill.setGoodId(goodId);
            seckill.setNum(1);
            if (this.save(seckill)) {
                try {
                    return orderService.submit(userId, goodId, 1);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @SentinelResource(value = "seckillForRedis", blockHandler = "seckillBlock", fallback = "seckillFallback")
    public Boolean seckillForRedis(Integer userId, Long goodId) {
        /*
            测机：八代i7，16g内存，mac，NVMe ssd
            压机：六代i7，16g内存，windows
            1.512个线程，tps保持在500左右，目前redis连接池，mysql连接池，redis减扣库存
            2.mysql连接池优化后，tps在1000左右
            3.使用redis暂存订单信息，tps在2300左右
         */
        // 单用户压测，暂时去掉用户验证
        /*Boolean hasSeckill = redisTemplate.opsForHash().hasKey(RedisKeyConstant.ORDER_HASH_USER_SECKILL, String.valueOf(userId));
        if (hasSeckill) {
            return false;
        }*/
        Object o = redisTemplate.opsForValue().get(RedisKeyConstant.STOCK_STR_LOCK);
        if (o != null) {
            return false;
        }
        List<String> keys = Lists.newArrayList();
        keys.add(RedisKeyConstant.STOCK_STR_LOCK);
        keys.add(RedisKeyConstant.ORDER_STR_SECKILL + goodId);
        Long res = stockService.decrStock(keys, 1);
        if (res < 0) {
            // stockService.subtract(goodId, -1);
            log.debug("库存空了！！");
            return false;
        }
        redisTemplate.opsForHash().put(RedisKeyConstant.ORDER_HASH_USER_SECKILL, String.valueOf(userId), 1);
        return orderService.submitForRedis(userId, goodId, 1);
    }

    public Boolean seckillBlock(Integer userId, Long goodId, BlockException e) {
        log.error("限流异常");
        return false;
    }

    public Boolean seckillFallback(Integer userId, Long goodId, Throwable t) {
        log.error("降级异常");
        return false;
    }

}
